import sys
import paddle
from paddlex import create_model
from graphviz import Source
import fitz  # PyMuPDF
from gradio_image_prompter import ImagePrompter
import numpy as np
from PIL import Image
import io
import time
import logging
from requests.exceptions import HTTPError, ConnectionError, Timeout
from openai import OpenAI  # 导入 OpenAI 客户端
import os
import tempfile
import zipfile
import base64
import pandas as pd
import gradio as gr
from docx import Document
from docx.oxml.table import CT_Tbl
from docx.oxml.text.paragraph import CT_P
from docx.text.paragraph import Paragraph
from docx.table import Table
from bs4 import BeautifulSoup
import win32com.client as win32
import pickle


# 设置环境变量以完全禁用 Gradio 的分析功能
os.environ['GRADIO_ANALYTICS_ENABLED'] = 'false'

# 配置日志记录
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# 用于解题和pdf自动批改的api
NLP_client = OpenAI(
    api_key="de15d4a8baae4c33df6f5440cf228671841686ca",
    # 含有 AI Studio 访问令牌的环境变量，https://aistudio.baidu.com/account/accessToken,
    base_url="https://aistudio.baidu.com/llm/lmapi/v3",  # aistudio 大模型 api 服务域名
)
NLP_model = "deepseek-r1"

# 用于解析流程图和docx文件批量批改的api
API_KEY = "sk-aad54d373d2747c08c2b649d932a4183"
client = OpenAI(api_key=API_KEY, base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")

# 定义用户数据文件路径
USER_DATA_FILE = 'users.pkl'

# 加载用户数据
if os.path.exists(USER_DATA_FILE):
    with open(USER_DATA_FILE, 'rb') as file:
        users = pickle.load(file)
else:
    users = {}# 存储用户信息的字典

# 创建临时目录用于解压 zip 文件和保存图片
temp_dir = tempfile.mkdtemp()
img_temp_dir = tempfile.mkdtemp()

# 全局变量用于存储批改记录
records = []
ungraded_files = []

# 存储未批改文件及其原因
unprocessed_files = {}

# 全局变量用于存储pdf文件信息
extracted_files = []
selected_pdf_folder = ""
evaluation_data = {}  # 存储所有文件的评估数据


# 公式解析
def extract_question(file_obj: gr.File) -> str:
    img_path = file_obj.name
    print(f"图片路径: {img_path}")
    
    # 首先尝试使用备用方案（大模型API）
    try:
        print(f"尝试使用大模型API解析公式")
        
        # 将图像转换为Base64编码
        base64_image = image_to_base64(img_path)
        
        # 使用现有的客户端和模型调用大模型API进行OCR
        completion = client.chat.completions.create(
            model="qwen-vl-max-latest",
            messages=[
                {
                    "role": "system",
                    "content": [{
                        "type": "text",
                        "text": "你是一个擅长识别数学公式的助手。请识别图片中的数学公式，并以LaTeX格式返回结果。请仅返回LaTeX代码，不要包含任何解释性文本。"
                    }],
                },
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": base64_image
                            },
                        },
                        {"type": "text", "text": "请识别图片中的数学公式，并以LaTeX格式返回结果。"},
                    ],
                },
            ],
        )
        
        # 处理响应
        response_data = completion.model_dump()
        latex_code = response_data.get('choices', [{}])[0].get('message', {}).get('content', '').strip()
        
        # 清理LaTeX代码（移除可能的格式标记）
        if latex_code.startswith('```latex'):
            latex_code = latex_code[len('```latex'):].strip()
        if latex_code.endswith('```'):
            latex_code = latex_code[:-len('```')].strip()
        
        # 确保output目录存在
        os.makedirs("./output", exist_ok=True)
        
        # 保存结果到JSON文件
        with open("./output/res.json", "w", encoding="utf-8") as f:
            import json
            json.dump({"rec_text": latex_code}, f, ensure_ascii=False, indent=2)
        
        reply = f"$${latex_code}$$"
        print(f"大模型API解析成功，结果: {reply}")
        return reply, reply
    except Exception as api_error:
        print(f"大模型API解析失败: {str(api_error)}")
        
        # 再尝试使用原始的PaddlePaddle模型方法
        try:
            # 添加更多调试信息
            print(f"Python版本: {sys.version}")
            print(f"PaddlePaddle版本: {paddle.__version__ if 'paddle' in sys.modules else '未导入'}")
            
            # 尝试直接配置PaddlePaddle使用CPU
            if 'paddle' in sys.modules:
                paddle.set_device('cpu')
                print(f"PaddlePaddle设备设置: CPU")
            
            # 设置环境变量强制使用CPU模式
            os.environ['CUDA_VISIBLE_DEVICES'] = '-1'  # 强制使用CPU
            os.environ['FLAGS_allocator_strategy'] = 'auto_growth'
            
            print(f"开始加载LaTeX_OCR_rec模型...")
            model = create_model("LaTeX_OCR_rec")
            print(f"模型加载成功")
            
            print(f"开始预测图片: {img_path}")
            output = model.predict(img_path, batch_size=1)
            print(f"预测完成，输出类型: {type(output)}")
            
            for res in output:
                res.print(json_format=False)
                # 确保output目录存在
                os.makedirs("./output", exist_ok=True)
                res.save_to_json("./output/res.json")
                reply = f"$${res['rec_text']}$$"
            return reply, reply
        except Exception as paddle_error:
            print(f"PaddlePaddle模型解析失败: {str(paddle_error)}")
            # 添加详细的异常信息
            import traceback
            print(f"详细错误信息:\n{traceback.format_exc()}")
            
            return "解析公式失败，请稍后再试", "解析公式失败，请稍后再试"

# 将图像文件转换为Base64编码格式，用于解析流程图
def image_to_base64(image_path):
    with open(image_path, "rb") as image_file:
        encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
    return f"data:image/png;base64,{encoded_string}"

#批量转换图片为Base64编码，用于pdf作业自动批改
def images_to_base64(images):
    """批量转换图片为Base64编码"""
    base64_list = []
    for img in images:
        if isinstance(img, Image.Image):
            buffered = io.BytesIO()
            img.save(buffered, format="PNG", quality=75)  # 压缩图片以减少传输时间
            img_byte_arr = buffered.getvalue()
            base64_str = base64.b64encode(img_byte_arr).decode('utf-8')
            base64_list.append(base64_str)
        elif isinstance(img, np.ndarray):
            img_pil = Image.fromarray(img)
            buffered = io.BytesIO()
            img_pil.save(buffered, format="PNG", quality=75)  # 压缩图片以减少传输时间
            img_byte_arr = buffered.getvalue()
            base64_str = base64.b64encode(img_byte_arr).decode('utf-8')
            base64_list.append(base64_str)
        else:
            raise ValueError("Unsupported image type")
    return base64_list

# 解析流程图
def extract_flowchart(file_obj: gr.File) -> (str, str):
    img_path = file_obj.name
    print(f"流程图路径: {img_path}")

    base64_image = image_to_base64(img_path)

    completion = client.chat.completions.create(
        model="qwen-vl-max-latest",
        messages=[
            {
                "role": "system",
                "content": [{"type": "text",
                             "text": "你是一个控制论老师，你能够根据用户提供的控制系统框图，对框图内容进行提取，包括框图形状和符号，并输出包含图中所有信息的dot文件,同时框图之间不能全都挤在一起。请仅返回合理的DOT代码，不要附加任何解释性文本。"}],
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {
                            "url": base64_image
                        },
                    },
                    {"type": "text", "text": "输出dot文件"},
                ],
            },
        ],
    )

    try:
        response_data = completion.model_dump()
        dot_code = response_data.get('choices', [{}])[0].get('message', {}).get('content', '')

        # 打印原始响应内容以供调试
        print(f"原始响应内容:\n{dot_code}")

        # 提取 digraph ControlSystem {} 内容
        start_index = dot_code.find("digraph ControlSystem {")
        end_index = dot_code.rfind("}")

        if start_index != -1 and end_index != -1:
            dot_code = dot_code[start_index:end_index+1]

        # 清理dot代码中的多余空格和换行符
        dot_code = "\n".join(line.strip() for line in dot_code.splitlines() if line.strip())

        print(f"清理后的dot代码:\n{dot_code}")  # 打印清理后的dot代码以供调试

        with open("flowchart.dot", "w", encoding="utf-8") as file:
            file.write(dot_code)

        print("DOT代码已成功保存到flowchart.dot文件中，并可由dot使用")
    except Exception as e:
        print(f"处理或保存DOT代码时发生错误: {e}")
        dot_code = "处理或保存DOT代码时发生错误"

    return img_path, dot_code

# 使用Graphviz生成流程图
def generate_flowchart_from_dot(dot_code):
    try:
        # 确保 dot_code 是有效的 DOT 语法
        if not dot_code.startswith("digraph"):
            raise ValueError("DOT代码不是以 'digraph' 开头")

        # 添加字体设置到现有的 digraph 中
        dot_code_with_font = f"""
digraph G {{
    fontname="Microsoft YaHei";  // 设置默认字体为微软雅黑
    node [fontname="Microsoft YaHei"];  // 设置节点字体为微软雅黑
    edge [fontname="Microsoft YaHei"];  // 设置边字体为微软雅黑
    {dot_code[len("digraph ControlSystem {"):-1]}
}}
"""

        # 打印最终的dot代码以供调试
        print(f"最终的dot代码:\n{dot_code_with_font}")

        # 将dot代码写入文件以便手动检查
        with open("final_flowchart.dot", "w", encoding="utf-8") as file:
            file.write(dot_code_with_font)

        # 创建Graphviz Source对象
        graph = Source(dot_code_with_font, format='png', engine='dot')

        # 渲染为PNG格式
        png_bytes = graph.pipe(format='png')

        # 将PNG数据编码为base64字符串
        image_url = "data:image/png;base64," + base64.b64encode(png_bytes).decode('utf-8')

        return image_url, dot_code_with_font
    except Exception as e:
        print(f"生成流程图时发生错误: {e}")
        return None, f"生成流程图时发生错误: {e}"

# 调用api，对用户输入的问题进行解答
def chat(user_input, messages):
    if len(messages) == 0:
        messages.append({
            "role": "system",
            "content": "你是一名教育工作者，从事高等数学教学，可以准确回答学生提出的问题。"
        })

    # 检查是否为dot代码
    if user_input.strip().startswith("digraph") or user_input.strip().startswith("graph"):
        dot_code = user_input.strip()
        print(f"正在处理的dot代码:\n{dot_code}")  # 打印dot代码以供调试

        # 清理dot代码中的多余空格和换行符
        dot_code = "\n".join(line.strip() for line in dot_code.splitlines() if line.strip())

        print(f"清理后的dot代码:\n{dot_code}")  # 打印清理后的dot代码以供调试

        image_url, error_message = generate_flowchart_from_dot(dot_code)
        if image_url:
            reply = f"![Flowchart]({image_url})"
        else:
            reply = error_message
    else:
        messages.append({'role': 'user', 'content': f"{user_input}"})

        try:
            completion = NLP_client.chat.completions.create(
                model=NLP_model,
                messages=messages,
                temperature=0.6,
            )
            reply = completion.choices[0].message.content
        except Exception as e:
            error_str = str(e)
            print(f"Error in generating response: {error_str}")
            
            # 根据错误类型提供更具体的错误消息
            if "token 已经用完毕" in error_str:
                reply = "当前AI服务的token已经用尽，请联系管理员充值后使用。"
            elif "404" in error_str or "not found" in error_str.lower():
                reply = "无法找到请求的资源，请检查输入或稍后再试。"
            elif "timeout" in error_str.lower() or "连接超时" in error_str:
                reply = "网络连接超时，请检查网络状况并稍后再试。"
            elif "401" in error_str or "unauthorized" in error_str.lower():
                reply = "认证失败，请联系管理员。"
            else:
                # 尝试使用备用的客户端（如果可用）
                try:
                    print("尝试使用备用AI服务...")
                    # 使用已经配置的client作为备用方案
                    backup_completion = client.chat.completions.create(
                        model="qwen-vl-max-latest",
                        messages=messages,
                        temperature=0.6,
                    )
                    reply = backup_completion.choices[0].message.content
                except Exception as backup_error:
                    print(f"备用AI服务也失败: {str(backup_error)}")
                    reply = "抱歉，我遇到了一些技术问题，请稍后再试。"

    messages.append({'role': "assistant", 'content': reply})

    return "", messages  # 返回空字符串以清空输入框

# 处理 zip 文件上传和解压（pdf）
def handle_upload_pdf(zip_file_path):
    global extracted_files

    extracted_files = []

    try:
        logging.info(f"Uploaded Zip File Path: {zip_file_path}")

        if not os.path.exists(zip_file_path):
            raise FileNotFoundError("上传的 ZIP 文件不存在")

        if not os.access(zip_file_path, os.R_OK):
            raise PermissionError("无权读取上传的 ZIP 文件")

        with zipfile.ZipFile(zip_file_path, "r") as zip_ref:
            zip_ref.extractall(temp_dir)

        for root, _, files in os.walk(temp_dir):
            for file in files:
                extracted_files.append(os.path.join(root, file))

        file_names = [os.path.basename(file) for file in extracted_files]

        return extracted_files, gr.update(choices=file_names, value=None), "文件夹已上传，请选择文件查看内容"
    except zipfile.BadZipFile:
        return [], gr.update(choices=[], value=None), "上传的文件不是有效的 ZIP 文件，请重新上传。"
    except Exception as e:
        return [], gr.update(choices=[], value=None), f"上传文件时发生错误: {str(e)}"

#上传pdf作业题目和标准答案
def get_homework(assignment_number, question_id):
    try:
        assignment_number = int(assignment_number)
        question_id = int(question_id)

        base_path = os.path.expanduser(r"~\Desktop\作业合集")
        question_image_path = os.path.join(base_path, f"作业{assignment_number}", f"题目{question_id}", "题目.png")
        answer_image_path = os.path.join(base_path, f"作业{assignment_number}", f"题目{question_id}", "答案.png")

        logging.info(f"题目图片路径: {question_image_path}")
        logging.info(f"答案图片路径: {answer_image_path}")

        if not os.path.exists(question_image_path):
            return ["找不到题目", "", ""]

        if not os.path.exists(answer_image_path):
            logging.info("答案图片不存在")
            return [Image.open(question_image_path), None, "无标准答案"]
        else:
            return [Image.open(question_image_path), Image.open(answer_image_path), ""]

    except Exception as e:
        logging.error(f"发生错误: {e}")
        return ["输入错误，请检查作业次数和题号是否为数字", None, ""]

#提取pdf第三页的图片并显示出来
def extract_third_page_all_images_pdf(pdf_path):
    pdf_document = fitz.open(pdf_path)

    if len(pdf_document) < 3:
        return None, "PDF 文件少于三页。"

    page = pdf_document.load_page(2)  # 第三页 (索引从 0 开始)
    images = page.get_images(full=True)

    if not images:
        return None, "第三页没有图片。"

    image_sources = []

    for img_index, img in enumerate(images):
        xref = img[0]
        base_image = pdf_document.extract_image(xref)
        image_bytes = base_image["image"]
        image_ext = base_image["ext"]

        image_path = os.path.join(temp_dir, f"page_3_img_{img_index}.{image_ext}")

        with open(image_path, "wb") as f:
            f.write(image_bytes)

        img_base64 = base64.b64encode(open(image_path, "rb").read()).decode()
        img_src = f'data:image/{image_ext};base64,{img_base64}'
        image_sources.append((image_path, img_src))

    return image_sources, None

#显示pdf第三页的图片
def display_file_content(selected_filename):
    global selected_pdf_folder

    if not selected_filename:
        return "", ""

    file_content = ""
    export_status = ""

    try:
        selected_file_path = next(
            (file for file in extracted_files if os.path.basename(file) == selected_filename), None
        )

        logging.info(f"Selected File Path: {selected_file_path}")

        if not selected_file_path:
            raise FileNotFoundError("选定的文件未找到")

        if selected_file_path.lower().endswith(".pdf"):
            img_sources, error_message = extract_third_page_all_images_pdf(selected_file_path)
            if error_message:
                file_content = error_message
            else:
                img_tags = ''.join(
                    [f'<img id="pdf-image-{i}" src="{src}" alt="图片裁剪区域"/>' for i, (_, src) in
                     enumerate(img_sources)])
                file_content = img_tags

                pdf_name = os.path.splitext(os.path.basename(selected_file_path))[0]
                desktop = os.path.expanduser("~/Desktop")
                selected_pdf_folder = os.path.join(desktop, pdf_name)
                os.makedirs(selected_pdf_folder, exist_ok=True)
        else:
            with open(selected_file_path, "r", encoding="utf-8") as f:
                content = f.read()
            file_content = content
    except Exception as e:
        file_content = f"无法读取文件内容: {str(e)}"
        logging.error(f"Error reading file: {str(e)}")

    return file_content, export_status

#第一张学生作业图片的裁剪
def process_input(data, state):
    logging.info(f"Type of data['image']: {type(data['image'])}")

    if isinstance(data['image'], Image.Image):
        img = np.array(data['image'])
    else:
        img = np.array(Image.fromarray(data['image']))

    new_points = data['points']

    if state is None:
        state = {
            "img": img.copy(),
            "rect": (0, 0, 1, 1),
            "points": None,
        }

    img2 = state["img"]
    rect = state["rect"]

    if new_points:
        for prompt in new_points:
            x1, y1, x2, y2 = int(prompt[0]), int(prompt[1]), int(prompt[3]), int(prompt[4])
            rect = (x1, y1, x2 - x1, y2 - y1)
            cropped_img = img[y1:y2, x1:x2]
            cropped_pil_img = Image.fromarray(cropped_img)
            state["cropped_img"] = cropped_pil_img
            state["rect"] = rect
            state["points"] = new_points
            return cropped_pil_img, state

    state = {"img": img2, "rect": rect, "points": new_points}
    pil_img = Image.fromarray(img2)
    return pil_img, state

#第二张学生作业图片的裁剪，即作业中的框图
def process_second_crop(data, state):
    logging.info(f"Type of second crop data['image']: {type(data['image'])}")

    if isinstance(data['image'], Image.Image):
        img = np.array(data['image'])
    else:
        img = np.array(Image.fromarray(data['image']))

    new_points = data['points']

    if state is None:
        state = {
            "img": img.copy(),
            "rect": (0, 0, 1, 1),
            "points": None,
        }

    img2 = state["img"]
    rect = state["rect"]

    if new_points:
        for prompt in new_points:
            x1, y1, x2, y2 = int(prompt[0]), int(prompt[1]), int(prompt[3]), int(prompt[4])
            rect = (x1, y1, x2 - x1, y2 - y1)
            cropped_img = img[y1:y2, x1:x2]
            cropped_pil_img = Image.fromarray(cropped_img)
            state["cropped_img"] = cropped_pil_img
            state["rect"] = rect
            state["points"] = new_points
            return cropped_pil_img, state

    state = {"img": img2, "rect": rect, "points": new_points}
    pil_img = Image.fromarray(img2)
    return pil_img, state

#保存裁剪后的作业图片和作业批改情况
def save_cropped_images(state_question, state_chart, question_number, comments, score, correction_result, dot_code, comparison_result):
    global selected_pdf_folder, evaluation_data
    messages = []

    if state_question and "cropped_img" in state_question:
        filename_question = f"question_{question_number}.png"
        state_question["cropped_img"].save(os.path.join(selected_pdf_folder, filename_question))
        messages.append(f"题目图片已保存为 {filename_question}")
    else:
        messages.append("请先选择要裁剪的题目区域")

    if state_chart and "cropped_img" in state_chart:
        filename_chart = f"chart_{question_number}.png"
        state_chart["cropped_img"].save(os.path.join(selected_pdf_folder, filename_chart))
        messages.append(f"图表图片已保存为 {filename_chart}")
    else:
        messages.append("请先选择要裁剪的图表区域")

    file_name = os.path.basename(selected_pdf_folder)
    if file_name not in evaluation_data:
        evaluation_data[file_name] = {}

    if question_number not in evaluation_data[file_name]:
        evaluation_data[file_name][question_number] = {
            "评分": score,
            "评语": comments,
            "自动评语": correction_result,
            "DOT代码": dot_code,
            "对比结果": comparison_result
        }
    else:
        evaluation_data[file_name][question_number]["评分"] = score
        evaluation_data[file_name][question_number]["评语"] = comments
        evaluation_data[file_name][question_number]["自动评语"] = correction_result
        evaluation_data[file_name][question_number]["DOT代码"] = dot_code
        evaluation_data[file_name][question_number]["对比结果"] = comparison_result

    all_evaluations = []
    for file, questions in evaluation_data.items():
        row = {"文件名": file}
        for q_num, eval_dict in sorted(questions.items(), key=lambda item: int(item[0])):
            row[f"题号{q_num}_评分"] = eval_dict["评分"]
            row[f"题号{q_num}_评语"] = eval_dict["评语"]
            row[f"题号{q_num}_自动评语"] = eval_dict["自动评语"]
            row[f"题号{q_num}_DOT代码"] = eval_dict["DOT代码"]
            row[f"题号{q_num}_对比结果"] = eval_dict["对比结果"]
        all_evaluations.append(row)

    excel_path = os.path.expanduser("~/Desktop/evaluations.xlsx")
    df = pd.DataFrame(all_evaluations)
    df.to_excel(excel_path, index=False)

    messages.append(f"评估数据已保存到 {excel_path}")

    return "\n".join(messages)

#构造图片内容对象（符合ERNIE API格式要求）
def get_content_image_obj(image_base64):
    """
    参数:image_base64 (str): Base64编码的图片字符串
    """
    return {
        "type": "image_url",
        "image_url": {
            "url": f"data:image/png;base64,{image_base64}"
        }
    }

#封装对API的调用，并且在遇到特定类型的异常时自动重试请求。
def make_api_call_with_retry(func, *args, max_retries=3, backoff_factor=0.3, **kwargs):
    retries = 0
    while retries < max_retries:
        try:
            response = func(*args, **kwargs)
            return response
        except (HTTPError, ConnectionError, Timeout) as e:
            logging.warning(f"Request failed with error: {e}, retrying...")
            time.sleep(backoff_factor * (2 ** retries))
            retries += 1
        except Exception as e:
            logging.error(f"Unexpected error: {e}")
            break
    logging.error("Max retries reached, giving up.")
    return None

#调用AI接口进行pdf作业批改或DOT代码对比
def ai_correction(image_base64_list: list, text_instruction: str) -> str:
    message_content = [{
        "type": "text",
        "text": text_instruction
    }]

    for image_base64 in image_base64_list:
        message_content.append(get_content_image_obj(image_base64))

    start_time = time.time()
    response = make_api_call_with_retry(NLP_client.chat.completions.create,
                                        model="ernie-4.5-8k-preview",
                                        messages=[{
                                            "role": "user",
                                            "content": message_content
                                        }],
                                        stream=False)
    end_time = time.time()
    logging.info(f"AI Process took {end_time - start_time} seconds")

    if response is not None:
        return response.choices[0].message.content
    else:
        return "AI Process failed after multiple attempts."

#调用AI接口生成DOT代码
def generate_dot_code(image_base64_list: list) -> str:
    return ai_correction(image_base64_list, "生成图片的DOT代码")

#比较用户提交的DOT代码和标准答案的DOT代码
def compare_dot_codes(dot_code_user: str, dot_code_standard: str) -> str:
    """
    参数:dot_code_user (str): 用户提交的DOT代码
        dot_code_standard (str): 标准答案的DOT代码
    """
    text_instruction = f"比较以下两个DOT代码，给出评分和评语。\n\n标准答案DOT代码：\n{dot_code_standard}\n\n用户提交的DOT代码：\n{dot_code_user}"
    return ai_correction([], text_instruction)

#dot代码批改
def auto_correct_question(first_cropped_img, second_cropped_img, question_image, answer_image, answer_text_output):
    if first_cropped_img is None or not isinstance(first_cropped_img, (Image.Image, np.ndarray)):
        return "缺少题目图片，请检查并重新上传。", ""

    base64_list = images_to_base64([first_cropped_img])

    if answer_image is not None and answer_text_output != "无标准答案":
        base64_list.append(images_to_base64([answer_image])[0])

    start_time = time.time()
    correction_result = ai_correction(base64_list, "你是一个控制论老师，批改图片中的作业，给出评语和评分")
    end_time = time.time()
    logging.info(f"Auto Correction Process took {end_time - start_time} seconds")

    if second_cropped_img is not None and isinstance(second_cropped_img, (Image.Image, np.ndarray)):
        start_time = time.time()
        dot_code = generate_dot_code(images_to_base64([second_cropped_img]))
        end_time = time.time()
        logging.info(f"Generate DOT Code Process took {end_time - start_time} seconds")
    else:
        dot_code = ""

    return correction_result, dot_code

#显示标准dot代码
def update_standard_answer_dot(assignment_number, question_id):
    try:
        # 获取桌面路径
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        excel_file_path = os.path.join(desktop_path, "standard_answers.xlsx")

        # 读取Excel文件
        df = pd.read_excel(excel_file_path)

        # 将输入转换为整数
        assignment_number = int(assignment_number)
        question_id = int(question_id)

        # 查找对应的标准答案
        filtered_df = df[(df['作业次数'] == assignment_number) & (df['题号'] == question_id)]

        if not filtered_df.empty:
            return filtered_df.iloc[0]['标准答案']
        else:
            return "无标准答案"

    except Exception as e:
        print(f"发生错误: {e}")
        return "输入错误，请检查作业次数和题号是否为数字"



# 处理 zip 文件上传和解压及批改——docx
def handle_upload(zip_file_path, grading_criteria):
    extracted_files_docx = []

    # 清空之前的记录
    records.clear()
    unprocessed_files.clear()

    try:
        # 解压 zip 文件到临时目录
        with zipfile.ZipFile(zip_file_path, "r") as zip_ref:
            zip_ref.extractall(temp_dir)

        # 获取解压后的所有文件路径
        for root, _, files in os.walk(temp_dir):
            for file in files:
                extracted_files_docx.append(os.path.join(root, file))

        # 批改每个文件
        for file_path in extracted_files_docx:
            if file_path.lower().endswith((".docx", ".doc")):
                feedback, _ = grade_homework(file_path, grading_criteria)
                if not feedback:
                    print(f"文件未被批改: {file_path}")

        # 汇总未批改文件的信息
        unprocessed_info = ""
        if unprocessed_files:
            unprocessed_info = "未批改的文件及其原因:\n"
            for file_path, reason in unprocessed_files.items():
                unprocessed_info += f"- {file_path}: {reason}\n"

        # 导出为 Excel 文件
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        excel_path = os.path.join(desktop_path, "graded_results.xlsx")
        df = pd.DataFrame(records)
        df.to_excel(excel_path, index=False)

        return f"Excel 文件已导出: {excel_path}", f"批改完成并导出成功！\n{unprocessed_info}"
    except zipfile.BadZipFile:
        return "", "上传的文件不是有效的 ZIP 文件，请重新上传。"
    except Exception as e:
        return "", f"处理文件时发生错误: {str(e)}"

# 提取 DOC 文档内容的函数
def extract_content_from_doc(file_path):
    try:
        word = win32.gencache.EnsureDispatch('Word.Application')
        word.Visible = False
        doc = word.Documents.Open(file_path)
        html_content = doc.Content.CopyHTML()
        doc.Close(False)
        word.Quit()
        return html_content
    except Exception as e:
        unprocessed_files[file_path] = f"提取 DOC 文件内容时出错: {str(e)}"
        return ""


# 提取纯文本内容（批改时忽略图片）
def extract_text_only_docx(doc):
    content_list = []
    for element in doc.element.body:
        if isinstance(element, CT_P):  # 段落
            paragraph = Paragraph(element, doc)
            if paragraph.text.strip():  # 只添加非空段落
                content_list.append(paragraph.text)
        elif isinstance(element, CT_Tbl):  # 表格
            table = Table(element, doc)
            for row in table.rows:
                for cell in row.cells:
                    cell_text = "".join([paragraph.text for paragraph in cell.paragraphs])
                    content_list.append(cell_text)

    return "\n".join(content_list)

# 提取纯文本内容（批改时忽略图片）从 HTML
def extract_text_from_html(html):
    soup = BeautifulSoup(html, 'html.parser')
    text = soup.get_text(separator='\n', strip=True)
    return text

# 解析 API 响应
def parse_response(response_text):
    feedback = ""

    lines = response_text.split("\n")
    in_feedback_section = False

    for line in lines:
        # 添加当前行到反馈中
        if line.startswith('### ') or line.startswith('**'):
            in_feedback_section = True
        if in_feedback_section:
            feedback += line + "\n"

    # 移除反馈末尾多余的换行符
    feedback = feedback.rstrip()

    return feedback

# 构造 API 请求内容
def construct_api_request(content, grading_criteria):
    return (
        f"请根据以下评分标准批改作业：\n{grading_criteria}\n\n"
        f"作业内容如下：\n{content}"
    )

# 截断输入内容以满足长度限制
def truncate_content(content, max_length=50000):
    if len(content) > max_length:
        print(f"输入内容过长，已截断至 {max_length} 字符。")
        return content[:max_length]
    return content


# 调用 API 进行批改（忽略图片）
def grade_homework(file_path, grading_criteria):
    # 提取文件内容（纯文本）
    try:
        if file_path.lower().endswith(".docx"):
            # 打开.docx文档
            doc = Document(file_path)

            # 提取纯文本内容（忽略图片）
            content = extract_text_only_docx(doc)
        elif file_path.lower().endswith(".doc"):
            # 打开.doc文档
            html_content = extract_content_from_doc(file_path)
            content = extract_text_from_html(html_content)
        else:
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()
    except Exception as e:
        unprocessed_files[file_path] = f"无法读取文件内容: {str(e)}"
        return "", ""

    # 构造 API 请求
    try:
        request_content = construct_api_request(content, grading_criteria)
        truncated_input = truncate_content(request_content)

        print(f"发送给 API 的请求内容: {file_path}, 内容: {truncated_input}")  # 调试日志

        response = client.chat.completions.create(
            model="qwen-max",  # 使用 Qwen-Max 模型
            messages=[
                {"role": "system", "content": "你是一个论文批改助手，负责根据评分标准生成评语和评分。"},
                {"role": "user", "content": truncated_input},
            ],
            max_tokens=500,
        )
        api_response = response.choices[0].message.content  # 获取 API 返回的内容
        print(f"API 响应内容: {file_path}, 内容: {api_response}")  # 打印完整响应内容
    except Exception as e:
        unprocessed_files[file_path] = f"API 调用失败: {str(e)}"
        return "", ""

    # 解析 API 响应
    feedback = parse_response(api_response)

    # 获取文件的基本名称
    filename = os.path.basename(file_path)

    # 存储记录
    record = {
        "filename": filename,
        "reference_feedback": feedback,
    }
    records.append(record)

    print(f"最终返回结果 - 评语: {file_path}, 评语: {feedback}")  # 调试日志
    return feedback, ""

# 注册函数
def register(role, username, password, confirm_password):
    if username in users:
        print(f"注册失败: 用户名已存在={username}")
        return False, "用户名已存在，请选择其他用户名。"
    elif password != confirm_password:
        print(f"注册失败: 密码不匹配={username}")
        return False, "两次输入的密码不一致，请重试。"
    else:
        users[username] = {'role': role, 'password': password}
        # 保存用户数据
        with open(USER_DATA_FILE, 'wb') as file:
            pickle.dump(users, file)
        print(f"注册成功: 用户名={username}, 角色={role}")
        return True, "注册成功，请登录。"

# 登录函数
def login(username, password):
    if username in users:
        if users[username]['password'] == password:
            role = users[username].get('role', None)
            print(f"登录成功: 用户名={username}, 角色={role}")
            return True, "", role
        else:
            print(f"登录失败: 用户名={username}, 密码错误")
            return False, "密码错误", None
    else:
        print(f"登录失败: 用户名={username}, 用户还未注册，请先注册")
        return False, "用户还未注册，请先注册", None

# 页面切换函数
def toggle_visibility(is_logged_in, role):
    print(f"toggle_visibility called with is_logged_in={is_logged_in}, role={role}")
    if is_logged_in:
        if role == "老师":
            print("Switching to teacher main page")
            return (
                gr.update(visible=False),  # 隐藏登录页面
                gr.update(visible=False),  # 隐藏注册页面
                gr.update(visible=True),   # 显示老师主页面
                gr.update(visible=False)   # 隐藏学生主页面
            )
        elif role == "学生":
            print("Switching to student main page")
            return (
                gr.update(visible=False),  # 隐藏登录页面
                gr.update(visible=False),  # 隐藏注册页面
                gr.update(visible=False),  # 隐藏老师主页面
                gr.update(visible=True)    # 显示学生主页面
            )
        else:
            print("Unknown role, switching back to login page")
            return (
                gr.update(visible=True),   # 显示登录页面
                gr.update(visible=False),  # 隐藏注册页面
                gr.update(visible=False),  # 隐藏老师主页面
                gr.update(visible=False)   # 隐藏学生主页面
            )
    else:
        print("Switching back to login page")
        return (
            gr.update(visible=True),      # 显示登录页面
            gr.update(visible=False),     # 隐藏注册页面
            gr.update(visible=False),     # 隐藏老师主页面
            gr.update(visible=False)      # 隐藏学生主页面
        )


def create_gui():
    with gr.Blocks(title="数字助教系统", theme="soft", analytics_enabled=False) as app:
        role_state = gr.State(value=None)  # 初始化角色状态
        login_page = gr.Row(visible=True)
        register_page = gr.Row(visible=False)
        main_app_teacher = gr.Row(visible=False)
        main_app_student = gr.Row(visible=False)

        #登录界面
        with login_page:
            with gr.Column(scale=1):
                gr.Markdown(" ")
            with gr.Column(scale=2):
                gr.Markdown("""# 登录""")
                tb_username_login = gr.Textbox(label="用户名", placeholder="请输入您的用户名")
                tb_password_login = gr.Textbox(label="密码", placeholder="请输入您的密码", type="password")
                btn_login = gr.Button("登录")
                lbl_error_login = gr.Label(label="错误信息")
                btn_register_link = gr.Button("注册新用户")

                btn_login.click(
                    fn=login,
                    inputs=[tb_username_login, tb_password_login],
                    outputs=[
                        gr.Checkbox(label="登录状态"),  # 绑定到登录状态
                        lbl_error_login,  # 绑定到错误信息
                        role_state  # 绑定到角色状态
                    ],
                    queue=False
                ).then(
                    fn=toggle_visibility,
                    inputs=[gr.Checkbox(label="确认登录"), role_state],
                    outputs=[login_page, register_page, main_app_teacher, main_app_student]
                )

                btn_register_link.click(
                    fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
                    outputs=[login_page, register_page]
                )

            with gr.Column(scale=1):
                gr.Markdown(" ")

        #注册界面
        with register_page:
            with gr.Column(scale=1):
                gr.Markdown(" ")
            with gr.Column(scale=2):
                gr.Markdown("""# 注册""")
                drp_role_register = gr.Dropdown(["学生", "老师"], label="请选择角色", value="学生")
                tb_username_register = gr.Textbox(label="用户名", placeholder="请输入您的用户名")
                tb_password_register = gr.Textbox(label="密码", placeholder="请输入您的密码", type="password")
                tb_confirm_password = gr.Textbox(label="确认密码", placeholder="请再次输入您的密码", type="password")
                btn_register = gr.Button("注册")
                lbl_error_register = gr.Label(label="错误信息")
                btn_back_to_login = gr.Button("返回登录")

                btn_register.click(
                    fn=register,
                    inputs=[drp_role_register, tb_username_register, tb_password_register, tb_confirm_password],
                    outputs=[gr.Checkbox(label="注册状态"), lbl_error_register],
                    queue=False
                ).then(
                    lambda _: (
                        gr.update(visible=False), gr.update(visible=True)
                    ) if _ else (
                        gr.update(visible=True), gr.update(visible=False)
                    ),
                    inputs=[gr.Checkbox(label="确认注册")],
                    outputs=[register_page, login_page]
                )

                btn_back_to_login.click(
                    fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
                    outputs=[register_page, login_page]
                )
            with gr.Column(scale=1):
                gr.Markdown(" ")

        #教师界面
        with main_app_teacher:
            with gr.Column(scale=2):
                gr.Markdown("""# 数字助教系统 - 教师版
                            你可以上传题目图片, 与助教聊天, 并获取答案。""")

                # 返回登录按钮
                with gr.Row():
                    gr.HTML("")  # 占位符，用于占满左侧空间
                    btn_back_to_login_teacher = gr.Button("返回登录界面", elem_classes=["top-right-button"])

                btn_back_to_login_teacher.click(
                    fn=lambda: (
                        gr.update(visible=False),  # 隐藏教师界面
                        gr.update(visible=True)  # 显示登录界面
                    ),
                    outputs=[main_app_teacher, login_page]
                )

                with gr.Tab("题目解析"):
                    file_upload_q = gr.File(
                        label="上传题目",
                        interactive=True,
                        file_types=['.png', '.jpg', '.jpeg'],
                    )
                    btn_upload_q = gr.Button("题目解析")
                    md_question = gr.Markdown(label="题干")
                    tb_question = gr.Textbox(label="题干(纯文本), 你可以将其复制到输入框中",
                                             placeholder="题目的纯文本描述")
                    md_solution = gr.Markdown(label="答案")

                    btn_upload_q.click(
                        fn=extract_question,
                        inputs=[file_upload_q],
                        outputs=[md_question, tb_question],
                    )

                with gr.Tab("流程图解析"):
                    file_upload_f = gr.File(
                        label="上传流程图",
                        interactive=True,
                        file_types=['.png', '.jpg', '.jpeg'],
                    )
                    btn_upload_f = gr.Button("解析流程图")
                    img_flowchart = gr.Image(label="流程图预览")
                    txt_dot_code = gr.Textbox(label="Dot代码", lines=10)

                    btn_upload_f.click(
                        fn=extract_flowchart,
                        inputs=[file_upload_f],
                        outputs=[img_flowchart, txt_dot_code],
                    )

                with gr.Tab("聊天"):
                    chatbot = gr.Chatbot(type="messages", render_markdown=True)
                    tb_user_input = gr.Textbox(
                        placeholder="与助教聊天或输入题目, 点击 '回车' 发送消息",
                        show_label=False,
                    )

                    tb_user_input.submit(
                        fn=chat,
                        inputs=[tb_user_input, chatbot],
                        outputs=[tb_user_input, chatbot],
                    )

                with gr.Tab("作业批改——pdf"):
                    zip_upload = gr.File(label="上传 .zip 压缩文件", type="filepath")

                    with gr.Row():
                        assignment_number_input = gr.Number(label="输入作业次数", precision=0)
                        question_id_input = gr.Number(label="输入题号", precision=0)

                    btn = gr.Button("查询")

                    with gr.Row():
                        question_output = gr.Image(label="题目", height=400)
                        answer_output = gr.Image(label="答案", height=400)
                        answer_text_output = gr.Textbox(label="答案提示", interactive=False)
                        standard_answer_dot_output = gr.Textbox(label="标准答案DOT", interactive=False)

                    def update_outputs(assignment_number, question_id):
                        question_image, answer_image, answer_info = get_homework(assignment_number, question_id)
                        standard_answer_dot = update_standard_answer_dot(assignment_number, question_id)

                        return question_image, answer_image, answer_info, standard_answer_dot

                    btn.click(
                        fn=update_outputs,
                        inputs=[assignment_number_input, question_id_input],
                        outputs=[question_output, answer_output, answer_text_output, standard_answer_dot_output]
                    )

                    file_dropdown = gr.Dropdown(label="选择文件", choices=[], interactive=True)

                    question_number_input = gr.Textbox(label="输入题号", placeholder="请输入题号")

                    comments_input = gr.Textbox(label="用户评语", placeholder="请输入评语")
                    score_input = gr.Number(label="评分", minimum=0, maximum=100)

                    save_button = gr.Button("保存图片和评分")

                    with gr.Row():
                        with gr.Column(scale=1):
                            file_content_output = gr.HTML(label="文件内容")
                            upload_result = gr.Textbox(label="上传结果")

                        with gr.Column(scale=2):
                            with gr.Row():
                                with gr.Column():
                                    first_cropper_state = gr.State()
                                    first_cropper_interface = gr.Interface(
                                        fn=process_input,
                                        inputs=[
                                            ImagePrompter(show_label=False),
                                            first_cropper_state
                                        ],
                                        outputs=[
                                            gr.Image(type="pil", show_label=False),
                                            first_cropper_state
                                        ],
                                        allow_flagging="never"  # 禁用 Flag 按钮
                                    )

                                with gr.Column():
                                    second_cropper_state = gr.State()
                                    second_cropper_interface = gr.Interface(
                                        fn=process_second_crop,
                                        inputs=[
                                            ImagePrompter(show_label=False),
                                            second_cropper_state
                                        ],
                                        outputs=[
                                            gr.Image(type="pil", show_label=False),
                                            second_cropper_state
                                        ],
                                        allow_flagging="never"  # 禁用 Flag 按钮
                                    )

                    zip_upload.upload(handle_upload_pdf, inputs=zip_upload,
                                      outputs=[gr.JSON(visible=False), file_dropdown, upload_result])
                    file_dropdown.change(display_file_content, inputs=file_dropdown,
                                         outputs=[file_content_output, gr.Textbox(value="", visible=False)])

                    correction_output = gr.Textbox(label="自动批改结果", interactive=False)
                    dot_code_output = gr.Textbox(label="DOT代码", interactive=False)
                    comparison_output = gr.Textbox(label="DOT对比结果", interactive=False)

                    save_button.click(save_cropped_images,
                                      inputs=[first_cropper_state, second_cropper_state, question_number_input,
                                              comments_input,
                                              score_input, correction_output, dot_code_output, comparison_output],
                                      outputs=upload_result)

                    # 自动批改部分
                    correct_button = gr.Button("自动批改")

                    correct_button.click(
                        fn=auto_correct_question,
                        inputs=[first_cropper_interface.output_components[0],
                                second_cropper_interface.output_components[0], question_output, answer_output,
                                answer_text_output],
                        outputs=[correction_output, dot_code_output]
                    )

                    # DOT代码对比部分
                    compare_button = gr.Button("对比DOT代码")

                    compare_button.click(
                        fn=lambda user_dot, standard_dot: compare_dot_codes(user_dot, standard_dot),
                        inputs=[dot_code_output, standard_answer_dot_output],
                        outputs=[comparison_output]
                    )

                with gr.Tab("批量批改——docx"):
                    # 文件上传组件
                    zip_upload = gr.File(label="上传 .zip 压缩文件", type="filepath")

                    # 评分标准输入框
                    grading_criteria = gr.Textbox(
                        label="请输入评分标准（JSON格式，例如：{'语法正确性': 30, '内容完整性': 40, '逻辑清晰度': 30}）",
                        value="{'语法正确性': 30, '内容完整性': 40, '逻辑清晰度': 30}",
                    )

                    # 批改按钮
                    grade_button = gr.Button("开始批改")

                    # 导出信息
                    export_info = gr.Textbox(label="导出状态", lines=4, interactive=False)

                    # 提示信息
                    upload_info = gr.Textbox(label="提示信息", lines=4, interactive=False)

                    # 使用 Gradio State 存储 extracted_files_docx
                    zip_upload.upload(
                        lambda x: ("", ""),
                        inputs=zip_upload,
                        outputs=[export_info, upload_info],
                    )
                    grade_button.click(
                        fn=handle_upload,
                        inputs=[zip_upload, grading_criteria],
                        outputs=[export_info, upload_info],
                    )

        with main_app_student:
            with gr.Column(scale=2):  # 扩大学生界面的宽度为原来的两倍
                gr.Markdown("""# 数字助教系统 - 学生版
                            你可以上传题目图片, 与助教聊天, 并获取答案。""")

                # 返回登录按钮
                with gr.Row():
                    gr.HTML("")  # 占位符，用于占满左侧空间
                    btn_back_to_login_student = gr.Button("返回登录界面", elem_classes=["top-right-button"])

                btn_back_to_login_student.click(
                    fn=lambda: (
                        gr.update(visible=False),  # 隐藏学生界面
                        gr.update(visible=True)  # 显示登录界面
                    ),
                    outputs=[main_app_student, login_page]
                )

                with gr.Tab("题目解析"):
                    file_upload_q = gr.File(
                        label="上传题目",
                        interactive=True,
                        file_types=['.png', '.jpg', '.jpeg'],
                    )
                    btn_upload_q = gr.Button("题目解析")
                    md_question = gr.Markdown(label="题干")
                    tb_question = gr.Textbox(label="题干(纯文本), 你可以将其复制到输入框中",
                                             placeholder="题目的纯文本描述")
                    md_solution = gr.Markdown(label="答案")

                    btn_upload_q.click(
                        fn=extract_question,
                        inputs=[file_upload_q],
                        outputs=[md_question, tb_question],
                    )



                with gr.Tab("流程图解析"):
                    file_upload_f = gr.File(
                        label="上传流程图",
                        interactive=True,
                        file_types=['.png', '.jpg', '.jpeg'],
                    )
                    btn_upload_f = gr.Button("解析流程图")
                    img_flowchart = gr.Image(label="流程图预览")
                    txt_dot_code = gr.Textbox(label="Dot代码", lines=10)

                    btn_upload_f.click(
                        fn=extract_flowchart,
                        inputs=[file_upload_f],
                        outputs=[img_flowchart, txt_dot_code],
                    )

                with gr.Tab("聊天"):
                    chatbot = gr.Chatbot(type="messages", render_markdown=True)
                    tb_user_input = gr.Textbox(
                        placeholder="与助教聊天或输入题目, 点击 '回车' 发送消息",
                        show_label=False,
                    )

                    tb_user_input.submit(
                        fn=chat,
                        inputs=[tb_user_input, chatbot],
                        outputs=[tb_user_input, chatbot],
                    )

    return app

if __name__ == '__main__':
    app = create_gui()
    app.queue()
    app.launch()